﻿using System;
using System.Collections;
using System.Configuration;
using System.Xml;
using System.Collections.Generic;

namespace LibUnitConverter {

    public class DefaultUnitConverter : IUnitConverter {

        private IUnitConverterLogger eLog = null;

        private Hashtable htDataStore = null;

        public string CoversionDataFile {
            get {
                return "";
            }
        }

        public DefaultUnitConverter() {
            htDataStore = GetConversionDataFromXML("ConversionData.xml");
            eLog = new UnitConverterLogger();
        }

        #region CreateDataMatrix(List<UnitData> aalConversionObjects)
        /// <summary>
        /// Create a Converstion Data Matrix.
        /// </summary>
        /// <param name="aalConversionObjects">List<UnitData> of populated UnitData objects</param>
        /// <returns>double array</returns>
        private double[,] CreateDataMatrix(List<UnitData> aalConversionObjects) {
            int iConvUnitCount = aalConversionObjects.Count;
            double[,] daMatrix = new double[iConvUnitCount, iConvUnitCount];
            string sCode = "";
            double dData = 0.0, dInternalData = 0.0;
            UnitData udUnitCode = null, udInternalCode = null;

            for (int i = 0; i < iConvUnitCount; i++) {
                udUnitCode = (UnitData)aalConversionObjects[i];
                sCode = udUnitCode.Code;
                dData = udUnitCode.Value;

                for (int j = 0; j < iConvUnitCount; j++) {
                    udInternalCode = (UnitData)aalConversionObjects[j];
                    dInternalData = udInternalCode.Value;
                    daMatrix[i, j] = dInternalData / dData;
                    udInternalCode = null;
                    dInternalData = 0.0;
                }

                udUnitCode = null;
                dData = 0.0;
            }

            return daMatrix;
        }
        #endregion

        #region GetConversionDataFromXML(string asFileName)
        /// <summary>
        /// Read application XML conversion file and return a Hashtable of conversion data.
        /// </summary>
        /// <param name="asFileName">Xml conversion data file name</param>
        /// <returns>Hashtable of conversions type and objects</returns>
        private Hashtable GetConversionDataFromXML(string asFileName) {
            asFileName = asFileName.Trim();

            //try {
                XmlTextReader Xreader = new XmlTextReader(asFileName);
                XmlDocument XmlDoc = new XmlDocument();
                XmlDoc.Load(Xreader);
                XmlNode topDocNode = XmlDoc.DocumentElement;
                return this.GetDataFromXMLNode(topDocNode);
            //} catch (Exception e) {
            //    eLog.Error("GetConversionDataFromXML", e.Message, "001");
            //}
            return null;
        }
        #endregion

        #region GetDataFromXMLNode(XmlNode axnDocNode)
        /// <summary>
        /// populate List<UnitData> of conversion UnitData objects and 
        /// fill conversion Hashtable with conversion type and 
        /// List<UnitData> of conversion UnitData.
        /// </summary>
        /// <param name="axnDocNode">XML conversion document node.</param>
        /// <returns>Hashtable of conversions type and objects</returns>
        private Hashtable GetDataFromXMLNode(XmlNode axnDocNode) {
            int iDataValues = 0;
            XmlNode DataNode = null;
            UnitData uoCurUnit = null;
            List<UnitData> alUnitData = null;
            Hashtable htConversionHash = new Hashtable();

            string sUnitType = "", sTempVal = "";

            //try {
                foreach (XmlNode curNode in axnDocNode.ChildNodes) {
                    alUnitData = new List<UnitData>();
                    sUnitType = curNode.Attributes["NAME"].Value;
                    iDataValues = curNode.ChildNodes.Count;

                    for (int i = 0; i < iDataValues; i++) {
                        DataNode = curNode.ChildNodes[i];
                        sTempVal = DataNode.Name;		// for Exception handling												
                        uoCurUnit = this.GetDataFromEachUnit(DataNode, sUnitType);

                        if (uoCurUnit != null)
                            alUnitData.Add(uoCurUnit);
                    }

                    htConversionHash.Add(sUnitType, alUnitData);
                    sUnitType = "";
                    DataNode = null;
                }
            //} catch (Exception ex) {
            //    eLog.Error("GetDataFromXMLNode", ex.Message + "\nMake Sure the XML file has not been courupted!", "001");
            //}
            return htConversionHash;
        }
        #endregion

        #region GetDataFromEachUnit(XmlNode axnDataNode, string asUnitType)
        /// <summary>
        /// Get UnitData object from a corresponding XML node
        /// </summary>
        /// <param name="axnDataNode"> XML node</param>
        /// <param name="asUnitType">The Unit Type</param>
        /// <returns>UnitData object</returns>
        private UnitData GetDataFromEachUnit(XmlNode axnDataNode, string asUnitType) {
            bool bNodeSucess = true;
            double dConversionVal = 0.0;

            string sDataType = "", sUnit = "",
                    sAttType = "", sValue = "", sErroInfo = "";
            int iAttribute = axnDataNode.Attributes.Count;

            try {
                for (int k = 0; k < iAttribute; k++) {
                    sAttType = axnDataNode.Attributes[k].Name.Trim().ToUpper();
                    sValue = axnDataNode.Attributes[k].Value;

                    switch (sAttType) {
                        case "CONVERSIONFACTOR":
                            dConversionVal = System.Convert.ToDouble(sValue);
                            break;

                        case "NAME":
                            sDataType = sValue;
                            break;

                        case "ABBREVIATION":
                            sUnit = sValue;
                            break;

                        default:
                            bNodeSucess = false;
                            eLog.Error("GetDataFromEachUnit", "Unrecognised XML Attribute :\n [" + asUnitType + "] < " + sDataType + " " + sAttType + " = " + sValue + "> \n Please Check the XML ConverstionData file.", "001");
                            break;
                    }

                    sAttType = "";
                    sValue = "";
                }

                if (bNodeSucess)
                    return new UnitData(sUnit, sDataType, dConversionVal);
            } catch (FormatException) {
                sErroInfo = "The string is not formatted as a double.\n< " + sDataType + " " + sAttType + " = " + sValue + "> \n Please Check the XML ConverstionData file.";
                eLog.Error("GetDataFromEachUnit", sErroInfo, "002");
            } catch (ArgumentNullException) {
                sErroInfo = "The string is null.\n< " + sDataType + " " + sAttType + " = " + sValue + "> \n Please Check the XML ConverstionData file.";
                eLog.Error("GetDataFromEachUnit", sErroInfo, "003");
            } catch (Exception ex) {
                sErroInfo = "Make Sure the XML file has not been courupted!\n Please Check the XML ConverstionData file.";
                eLog.Error("GetDataFromEachUnit", sErroInfo + "\n \n" + ex.Message, "004");
            }

            return null;
        }
        #endregion

        public int FindIndex(List<UnitData> alConvCode1, string unit) {
            for(int index = 0; index < alConvCode1.Count; index++) {
                UnitData data = (UnitData)alConvCode1[index];
                if (data.Text == unit) {
                    return index;
                }
            }

            throw new Exception("Cannot find the suitable unit.");
        }

        public List<UnitData> Find(string from) {
            foreach (List<UnitData> data in htDataStore.Values) {
                foreach (UnitData unit in data) {
                    if (unit.Text == from) {
                        return data;
                    }
                }
            }

            return null;
        }

        public double Convert(double value, string from, string to) {
            List<UnitData> alConvCode1 = Find(from);
            int iIndex2 = FindIndex(alConvCode1, from);
            int iIndex1 = FindIndex(alConvCode1, to);

            double[,] daDataMatrix 
                = this.CreateDataMatrix(alConvCode1);
            double dResult = value * daDataMatrix[iIndex2, iIndex1];
            return dResult;
        }

        public ICollection Catalogs {
            get {
                return this.htDataStore.Keys;
            }
        }

        public List<UnitData> GetUnits(string catalog) {
            return (List<UnitData>)this.htDataStore[catalog];
        }
    }
}
